#include "main.h"
#include "co_tests.h"

//**************************************
// test data
//**************************************
static void *default_input_value   = (void *) 0xDEADBABE;
static void *default_output_value  = (void *) 0xCAFEBEEF;


SUITE(co);

//**************************************
// TEST 1
//**************************************

static void co_test_1_coroutine(void *data)
{
}

TEST co_test_1(void)
{
    co_t co = co_create(co_test_1_coroutine, STACK_SIZE, NULL, "co_test_1-1");
    ASSERT_NEQm("created coroutine", co, NULL);
    ASSERT_STR_EQ("co_test_1-1", co_get_stash(co));

    void *rv = co_start(co, default_input_value);
    ASSERT_EQm("coroutine called (1)", rv, NULL);
    rv = co_resume(co, default_input_value);
    ASSERT_EQm("coroutine called (2)", rv, NULL);

    co_destroy(&co);
    ASSERT_EQm("coroutine destroyed", co, NULL);

    PASSm("empty coroutine called");
}

//**************************************
// TEST 2
//**************************************

static void co_test_2_coroutine(void *data)
{
    co_yield(data);
}

TEST co_test_2(void)
{
    co_t co = co_create(co_test_2_coroutine, STACK_SIZE, NULL, "co_test_2-1");

    void *rv = co_start(co, default_input_value);
    ASSERT_EQm("coroutine yielded appropriately", rv, default_input_value);
    rv = co_resume(co, default_input_value);
    ASSERT_EQm("coroutine terminated on exit", rv, NULL);

    co_destroy(&co);

    PASSm("coroutine ends safely");
}

//**************************************
// TEST 3
//**************************************

static void co_test_3_coroutine(void *data)
{
    while (true) { co_yield(data); }
}

TEST co_test_3(void)
{
    co_t co = co_create(co_test_3_coroutine, STACK_SIZE, NULL, "co_test_3-1");

    void *rv;

    for (int i = 0; i < 5; i++)
    {
        rv = co_resume(co, default_input_value);
        ASSERT_EQm("coroutine yielded repeatedly in loop", rv, default_input_value);
    }

    co_destroy(&co);

    PASSm("looping coroutine does not end");
}

//**************************************
// TEST 4
//**************************************

static void co_test_4_coroutine(void *data)
{
    co_yield(default_output_value);
}

TEST co_test_4(void)
{
    co_t co = co_create(co_test_4_coroutine, STACK_SIZE, NULL, "co_test_4-1");

    void *rv = co_start(co, default_input_value);
    ASSERT_EQm("coroutine yielded appropriately", rv, default_output_value);
    rv = co_resume(co, default_input_value);
    ASSERT_EQm("coroutine terminated on exit", rv, NULL);

    co_destroy(&co);

    PASSm("single-call coroutine yields its own data");
}

//**************************************
// TEST 5
//**************************************

static void co_test_5_coroutine(void *data)
{
    while (true) { co_yield(default_output_value); }
}

TEST co_test_5(void)
{
    co_t co = co_create(co_test_5_coroutine, STACK_SIZE, NULL, "co_test_5-1");

    void *rv;

    for (int i = 0; i < 5; i++)
    {
        rv = co_resume(co, default_input_value);
        ASSERT_EQm("coroutine yielded repeatedly in loop", rv, default_output_value);
    }

    co_destroy(&co);

    PASSm("looping coroutine yields its own data repeatedly");
}

//**************************************
// TEST 6
//**************************************

static void *test_6_innermost_data;
static void co_test_6_coroutine_b(void *data)
{
    test_6_innermost_data = data;
    co_yield(default_output_value);
}

static void co_test_6_coroutine_a(void *data)
{
    co_yield(co_resume(data, default_input_value));
}

static void co_test_6_coroutine(void *data)
{
    co_t co_b = co_create(co_test_6_coroutine_b, STACK_SIZE, NULL, "co_test_6-1");
    void *rv = co_resume(data, co_b);
    co_destroy(&co_b);
    co_yield(rv);
}

TEST co_test_6()
{
    co_t co = co_create(co_test_6_coroutine, STACK_SIZE, NULL, "co_test_6-2");
    co_t co_a = co_create(co_test_6_coroutine_a, STACK_SIZE, NULL, "co_test_6-3");

    void *rv = co_start(co, co_a);
    ASSERT_EQm("chained coroutines yielded proper value", rv, default_output_value);
    ASSERT_EQm("innermost coroutine got proper value", test_6_innermost_data, default_input_value);

    co_destroy(&co_a);
    co_destroy(&co);

    PASSm("coroutines can call indirectly and chained");
}

//**************************************
// Coroutine evaluation suite
//**************************************

SUITE(co)
{
    RUN_TEST(co_test_1);
    RUN_TEST(co_test_2);
    RUN_TEST(co_test_3);
    RUN_TEST(co_test_4);
    RUN_TEST(co_test_5);
    RUN_TEST(co_test_6);
}

